using System;
using System.Collections;
using System.IO;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Cms;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Utilities;

namespace Org.BouncyCastle.Cms
{
	/**
	* Parsing class for an CMS Authenticated Data object from an input stream.
	* <p>
	* Note: that because we are in a streaming mode only one recipient can be tried and it is important
	* that the methods on the parser are called in the appropriate order.
	* </p>
	* <p>
	* Example of use - assuming the first recipient matches the private key we have.
	* <pre>
	*      CMSAuthenticatedDataParser     ad = new CMSAuthenticatedDataParser(inputStream);
	*
	*      RecipientInformationStore  recipients = ad.getRecipientInfos();
	*
	*      Collection  c = recipients.getRecipients();
	*      Iterator    it = c.iterator();
	*
	*      if (it.hasNext())
	*      {
	*          RecipientInformation   recipient = (RecipientInformation)it.next();
	*
	*          CMSTypedStream recData = recipient.getContentStream(privateKey, "BC");
	*
	*          processDataStream(recData.getContentStream());
	*
	*          if (!Arrays.equals(ad.getMac(), recipient.getMac())
	*          {
	*              System.err.println("Data corrupted!!!!");
	*          }
	*      }
	*  </pre>
	*  Note: this class does not introduce buffering - if you are processing large files you should create
	*  the parser with:
	*  <pre>
	*          CMSAuthenticatedDataParser     ep = new CMSAuthenticatedDataParser(new BufferedInputStream(inputStream, bufSize));
	*  </pre>
	*  where bufSize is a suitably large buffer size.
	*/
	public class CmsAuthenticatedDataParser
		: CmsContentInfoParser
	{
		internal RecipientInformationStore	_recipientInfoStore;
		internal AuthenticatedDataParser	authData;

		private AlgorithmIdentifier			macAlg;
		private byte[]						mac;
		private Asn1.Cms.AttributeTable		authAttrs;
		private Asn1.Cms.AttributeTable		unauthAttrs;

		private bool authAttrNotRead;
		private bool unauthAttrNotRead;

		public CmsAuthenticatedDataParser(
			byte[] envelopedData)
			: this(new MemoryStream(envelopedData, false))
		{
		}

		public CmsAuthenticatedDataParser(
			Stream envelopedData)
			: base(envelopedData)
		{
			this.authAttrNotRead = true;
			this.authData = new AuthenticatedDataParser((Asn1SequenceParser)_contentInfo.GetContent(Asn1Tags.Sequence));

			// TODO Validate version?
			//DERInteger version = this._envelopedData.getVersion();

			//
			// load the RecipientInfoStore
			//
			Asn1SetParser s = authData.GetRecipientInfos();
			IList baseInfos = new ArrayList();

			Asn1Encodable entry;
			while ((entry = s.ReadObject()) != null)
			{
				baseInfos.Add(RecipientInfo.GetInstance(entry.ToAsn1Object()));
			}

			this.macAlg = authData.GetMacAlgorithm();

			//
			// read the encrypted content info
			//
			ContentInfoParser data = authData.GetEnapsulatedContentInfo();

			//
			// prime the recipients
			//
			IList infos = new ArrayList();
			Stream dataStream = ((Asn1OctetStringParser)data.GetContent(Asn1Tags.OctetString)).GetOctetStream();

			foreach (RecipientInfo info in baseInfos)
			{
				Asn1Encodable recipInfo = info.Info;

				if (recipInfo is KeyTransRecipientInfo)
				{
					infos.Add(new KeyTransRecipientInformation(
						(KeyTransRecipientInfo)recipInfo, null, macAlg, dataStream));
				}
				else if (recipInfo is KEKRecipientInfo)
				{
					infos.Add(new KEKRecipientInformation(
						(KekRecipientInfo)recipInfo, null, macAlg, dataStream));
				}
				else if (recipInfo is KeyAgreeRecipientInfo)
				{
					infos.Add(new KeyAgreeRecipientInformation(
						(KeyAgreeRecipientInfo)recipInfo, null, macAlg, dataStream));
				}
				else if (recipInfo is PasswordRecipientInfo)
				{
					infos.Add(new PasswordRecipientInformation(
						(PasswordRecipientInfo)recipInfo, null, macAlg, dataStream));
				}
			}

			_recipientInfoStore = new RecipientInformationStore(infos);
		}

		public AlgorithmIdentifier MacAlgorithmID
		{
			get { return macAlg; }
		}

		/**
		* return the object identifier for the mac algorithm.
		*/
		public string MacAlgOid
		{
			get { return macAlg.ObjectID.Id; }
		}

//		/**
//		* return the ASN.1 encoded encryption algorithm parameters, or null if
//		* there aren't any.
//		*/
//		public byte[] getMacAlgParams()
//		{
//		try
//		{
//		return encodeObj(macAlg.getParameters());
//		}
//		catch (Exception e)
//		{
//		throw new RuntimeException("exception getting encryption parameters " + e);
//		}
//		}
//
//		/**
//		* Return an AlgorithmParameters object giving the encryption parameters
//		* used to encrypt the message content.
//		*
//		* @param provider the name of the provider to generate the parameters for.
//		* @return the parameters object, null if there is not one.
//		* @throws org.bouncycastle.cms.CMSException if the algorithm cannot be found, or the parameters can't be parsed.
//		* @throws java.security.NoSuchProviderException if the provider cannot be found.
//		*/
//		public AlgorithmParameters getMacAlgorithmParameters(
//			string  provider)
//		{
//		return getMacAlgorithmParameters(CMSUtils.getProvider(provider));
//		}
//
//		/**
//		* Return an AlgorithmParameters object giving the encryption parameters
//		* used to encrypt the message content.
//		*
//		* @param provider the provider to generate the parameters for.
//		* @return the parameters object, null if there is not one.
//		* @throws org.bouncycastle.cms.CMSException if the algorithm cannot be found, or the parameters can't be parsed.
//		*/
//		public AlgorithmParameters getMacAlgorithmParameters(
//		Provider provider)
//		{
//		return CMSEnvelopedHelper.INSTANCE.getEncryptionAlgorithmParameters(getMacAlgOID(), getMacAlgParams(), provider);
//		}

		/**
		* return a store of the intended recipients for this message
		*/
		public RecipientInformationStore GetRecipientInfos()
		{
			return _recipientInfoStore;
		}

		public byte[] GetMac()
		{
			if (mac == null)
			{
				GetAuthAttrs();
				mac = authData.GetMac().GetOctets();
			}
			return Arrays.Clone(mac);
		}

		/**
		* return a table of the unauthenticated attributes indexed by
		* the OID of the attribute.
		* @exception java.io.IOException
		*/
		public Asn1.Cms.AttributeTable GetAuthAttrs()
		{
			if (authAttrs == null && authAttrNotRead)
			{
				Asn1SetParser s = authData.GetAuthAttrs();

				authAttrNotRead = false;

				if (s != null)
				{
					Asn1EncodableVector v = new Asn1EncodableVector();

					Asn1Encodable o;
					while ((o = s.ReadObject()) != null)
					{
						Asn1SequenceParser seq = (Asn1SequenceParser)o;

						v.Add(seq.ToAsn1Object());
					}

					authAttrs = new Asn1.Cms.AttributeTable(new DerSet(v));
				}
			}

			return authAttrs;
		}

		/**
		* return a table of the unauthenticated attributes indexed by
		* the OID of the attribute.
		* @exception java.io.IOException
		*/
		public Asn1.Cms.AttributeTable GetUnauthAttrs()
		{
			if (unauthAttrs == null && unauthAttrNotRead)
			{
				Asn1SetParser s = authData.GetUnauthAttrs();

				unauthAttrNotRead = false;

				if (s != null)
				{
					Asn1EncodableVector v = new Asn1EncodableVector();

					Asn1Encodable o;
					while ((o = s.ReadObject()) != null)
					{
						Asn1SequenceParser seq = (Asn1SequenceParser)o;

						v.Add(seq.ToAsn1Object());
					}

					unauthAttrs = new Asn1.Cms.AttributeTable(new DerSet(v));
				}
			}

			return unauthAttrs;
		}

		private byte[] encodeObj(
			Asn1Encodable obj)
		{
			return obj == null ? null : obj.GetEncoded();
		}
	}
}
